﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Timers;
using log4net;
using System.Configuration;
using Impinj.OctaneSdk;
using GS1.Utilitario;
using GS1.Entidad;


namespace HermesRFID1
{
    public partial class HermesRFID1 : ServiceBase
    {
        #region --- Variables y Propiedades ---
        Timer timerServicioLectura = new Timer();
        private static readonly ILog log = LogManager.GetLogger(typeof(HermesRFID1));

        // Create a collection to hold all the ImpinjReader instances.
        private List<ImpinjReader> readers = null;
        private ImpinjReader reader = null;

        private const int NUMERO_ANTENA_COMANDO = 1;
        private const int TIEMPO_ESPERA_COMANDO = 7500;

        private const string SIGNO_ESTACIONARIO = "Estacionario";

        private const string COMANDO_LECTURA = "LEC:{0}{1}";
        private const string COMANDO_DIRECCION = "DIR:{0}{1}{2}";
        
        private List<Etiqueta> listaEtiquetas = new List<Etiqueta>();

        private RFIDState estadoReader;
        private enum RFIDState
        {
            CONECTADO,
            DESCONECTADO
        }

        // Cabecera de identificacion de etiqueta
        private const string IDENTIFICADOR_CAB_ETIQUETA = "340833B2DDD94";
        #endregion

        #region --- Constructor y Destructor ---
        public HermesRFID1()
        {
            log4net.Config.XmlConfigurator.Configure();

            InitializeComponent();
        }
        #endregion

        #region --- Eventos del Servicio ---
        protected override void OnStart(string[] args)
        {
            try
            {
                log.Debug("------------------------------");
                log.Debug("Iniciando Servicio Lectura ... v20140530.0710");
                // ------
                log.Debug("Iniciando conexion con Reader ...");
                this.estadoReader = RFIDState.DESCONECTADO;

                bool resultado = conectar();
                if (resultado)
                {
                    log.Debug("Se establecio conexion con Reader - " + ConfigurationManager.AppSettings[Constantes.IPREADER]);

                    resultado = iniciarLectura();
                    if (resultado)
                    {
                        log.Debug("Se inicio la lectura del Reader");
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }
        }

        protected override void OnStop()
        {
            try
            {
                log.Debug("-------------------------------");
                log.Debug("Deteniendo Servicio Lectura ... v20140530.0710");

                // -------
                log.Debug("Deteniendo conexion con el  Reader ... ");
                detenerLectura();

                bool resultado = desconectar();
                if (resultado)
                {
                    log.Debug("Se finalizo conexion con Reader - " + ConfigurationManager.AppSettings[Constantes.IPREADER]);
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }
        }
        #endregion

        #region --- Evento del Timer ---
        private void timerServicioLectura_Elapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                timerServicioLectura.Stop();
                //Ejecutar Tareas
            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }
            finally
            {
                timerServicioLectura.Start();
            }
        }
        #endregion

        #region --- Eventos del Reader ---

        private void reader_TagsReported(ImpinjReader sender, TagReport report)
        {
            if (this.estadoReader == RFIDState.DESCONECTADO) { return; }

            try
            {

                // Validar si hay tag leidos
                if (args.TagReport.Tags.Count == 0) { return; }

                lock (this)
                {
                    Tag tag = args.TagReport.Tags[0];

                    // Validar que sea una etiqueta con la cabecera de servir
                    if (!esEtiquetaValida(tag.Epc)) { return; }

                    // Validar si esta activo el comando de Lectura de Tag
                    if (ConfigurationManager.AppSettings[Constantes.ACTIVAR_COMANDO_LEC].ToString().Equals(Constantes.SI))
                    {
                        // Verificar si el numero de antena es validar para control de lectura
                        if (esAntenaControlLectura(tag.AntennaPortNumber.ToString()))
                        {

                            enviarServidor(string.Format(COMANDO_LECTURA, tag.Epc + SEPARADOR_COMANDO,
                                                                          tag.AntennaPortNumber.ToString()));
                        }
                        else
                        {

                            //Obtiene los datos completos de la Etiqueta que leyó
                            Etiqueta etiqueta = new Etiqueta();
                            etiqueta.epc = tag.Epc;
                            etiqueta = new EtiquetaDA().obtenerEtiqueta(etiqueta);

                            //lee la hora en que validó la lectura de la etiqueta
                            etiqueta.fechaLectura = DateTime.Now;
                            //Limpia la lista antes de validar
                            LimpiaLista();
                            // Si el epc ya existe en listado entonces salir.
                            if (validarEpcRepetido(etiqueta)) { return; }

                            listaEtiquetas.Add(etiqueta);

                            // Estableciendo datos de la etiqueta a evaluar movimiento
                            MovimientoEtiqueta movimientoEtiqueta = new MovimientoEtiqueta();

                            // Se estabece el epc de la etiqueta
                            movimientoEtiqueta.etiqueta = new Etiqueta();

                            // Obteniendo datos completos de la Sede Actual para leer los correos actualizados
                            this.sedeActual = new SedeDA().obtenerSede(this.sedeActual);

                            // Se establece sede y motivo
                            movimientoEtiqueta.etiqueta = etiqueta;
                            movimientoEtiqueta.sede = this.sedeActual;
                            movimientoEtiqueta.motivo = this.motivoActual;

                            // Procesar movimiento de etiqueta
                            int alertaReponsables = new MovimientoEtiquetaDA().procesarMovimiento(movimientoEtiqueta);

                            if (log.IsDebugEnabled)
                            {
                                log.Debug("Procesamiento de Movimiento Alerta:" + alertaReponsables.ToString());
                            }

                            // Validar si hay que alerta
                            if (alertaReponsables == 1)
                            {
                                // Ejecutar envio de mail de notificacion
                                EmailHelper.enviarNotificacion(this.sedeActual, etiqueta);

                                if (log.IsDebugEnabled)
                                { log.Debug("Envio de mail OK"); }
                            }

                        }
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }
        }
        #endregion

        #region --- Procedimientos OctaneSDK ---
        private void inicializarReader()
        {
            //Se declara la lista de los readers con los que se va a trabajar
            this.readers = new List<ImpinjReader>();
        }

        private void finalizarReader()
        {
            foreach (ImpinjReader reader in this.readers)
            {
                reader.TagsReported -= OnTagsReported;
            }
            this.readers = null;
        }

        private bool conectar()
        {
            bool resultado = false;
            try
            {
                inicializarReader();

                // Crea la instancia del reader en la puerta de Produccion nacional.
                this.readers.Add(new ImpinjReader(ConfigurationManager.AppSettings[Constantes.IPREADER1], "Reader Produccion Nacional"));
                // Crea la instancia del reader en la puerta de Pacto Andino.
                this.readers.Add(new ImpinjReader(ConfigurationManager.AppSettings[Constantes.IPREADER2], "Reader Pacto Andino"));

                foreach (ImpinjReader reader in this.readers)
                {
                    reader.Connect();

                    Settings settings = reader.QueryDefaultSettings();
                    
                    reader.ClearSettings();

                    int indiceReaderMode = 0;
                    indiceReaderMode = Convert.ToInt32(ConfigurationManager.AppSettings[Constantes.INDICE_READER_MODE]);

                    // Estableciendo Reader Mode
                    switch (indiceReaderMode)
                    {
                        case 0://AutoSet Dense Reader
                            settings.ReaderMode = ReaderMode.AutoSetDenseReader;
                            break;
                        case 1://AutoSet Single Reader
                            settings.ReaderMode = ReaderMode.AutoSetSingleReader;
                            break;
                        case 2://Max Throughput
                            settings.ReaderMode = ReaderMode.MaxThroughput;
                            break;
                        case 3://Hybrid Mode
                            settings.ReaderMode = ReaderMode.Hybrid;
                            break;
                        case 4://Dense Reader (M=4)
                            settings.ReaderMode = ReaderMode.DenseReaderM4;
                            break;
                        case 5://Dense Reader (M=8)
                            settings.ReaderMode = ReaderMode.DenseReaderM8;
                            break;
                        case 6://MaxMiller
                            settings.ReaderMode = ReaderMode.MaxMiller;
                            break;
                    }

                    int indiceSearchMode = 0;
                    indiceSearchMode = Convert.ToInt32(ConfigurationManager.AppSettings[Constantes.INDICE_SEARCH_MODE]);

                    // Estableciendo Search Mode
                    switch (indiceSearchMode)
                    {
                        case 0://Dual Target
                            settings.SearchMode = SearchMode.DualTarget;
                            break;
                        case 1://Single Target Inventory
                            settings.SearchMode = SearchMode.SingleTarget;
                            break;
                        case 2://Single Target w/Supression
                            settings.SearchMode = SearchMode.SingleTargetWithSuppression;
                            break;
                        case 3://Desconocido
                            settings.SearchMode = SearchMode.ReaderSelected;
                            break;
                    }

                    // Estableciendo Sesion
                    settings.Session = Convert.ToUInt16(ConfigurationManager.AppSettings[Constantes.INDICE_SESSION]);

                    settings.Antennas[1].IsEnabled = ConfigurationManager.AppSettings[Constantes.ACTIVAR_ANTENA1].ToString().Equals(Constantes.SI);
                    settings.Antennas[2].IsEnabled = ConfigurationManager.AppSettings[Constantes.ACTIVAR_ANTENA2].ToString().Equals(Constantes.SI);
                    settings.Antennas[3].IsEnabled = ConfigurationManager.AppSettings[Constantes.ACTIVAR_ANTENA3].ToString().Equals(Constantes.SI);
                    settings.Antennas[4].IsEnabled = ConfigurationManager.AppSettings[Constantes.ACTIVAR_ANTENA4].ToString().Equals(Constantes.SI);

                    if (settings.Antennas[1].IsEnabled)
                    {
                        settings.Antennas[1].TxPowerInDbm = Convert.ToDouble(ConfigurationManager.AppSettings[Constantes.POTENCIA_ANTENA1]);
                        settings.Antennas[1].RxSensitivityInDbm = Convert.ToInt32(ConfigurationManager.AppSettings[Constantes.SENSIBILIDAD_ANTENA1]);
                    }

                    if (settings.Antennas[2].IsEnabled)
                    {
                        settings.Antennas[2].TxPowerInDbm = Convert.ToDouble(ConfigurationManager.AppSettings[Constantes.POTENCIA_ANTENA2]);
                        settings.Antennas[2].RxSensitivityInDbm = Convert.ToInt32(ConfigurationManager.AppSettings[Constantes.SENSIBILIDAD_ANTENA2]);
                    }

                    if (settings.Antennas[3].IsEnabled)
                    {
                        settings.Antennas[3].TxPowerInDbm = Convert.ToDouble(ConfigurationManager.AppSettings[Constantes.POTENCIA_ANTENA3]);
                        settings.Antennas[3].RxSensitivityInDbm = Convert.ToInt32(ConfigurationManager.AppSettings[Constantes.SENSIBILIDAD_ANTENA3]);
                    }

                    if (settings.Antennas[4].IsEnabled)
                    {
                        settings.Antennas[4].TxPowerInDbm = Convert.ToDouble(ConfigurationManager.AppSettings[Constantes.POTENCIA_ANTENA4]);
                        settings.Antennas[4].RxSensitivityInDbm = Convert.ToInt32(ConfigurationManager.AppSettings[Constantes.SENSIBILIDAD_ANTENA4]);
                    }

                    settings.Report.IncludeAntennaPortNumber = true;
                    settings.Report.IncludePeakRssi = true;
                    settings.Report.IncludePhaseAngle = true;
                    settings.Report.IncludeChannel = true;
                    settings.Report.IncludeFirstSeenTime = true;
                    settings.Report.Mode = ReportMode.Individual;

                    // Estableciendo los nuevos parametros de configuracion
                    this.reader.ApplySettings(settings);

                    // Estableciendo identificador(MAC) del ReaderRFID
                    this.reader.ReaderIdentity = this.reader.QueryFeatureSet().ReaderIdentity;
                }
                this.estadoReader = RFIDState.CONECTADO;

                resultado = true;
            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }

            return resultado;
        }

        private bool desconectar()
        {
            bool resultado = false;

            try
            {
                if (this.estadoReader == RFIDState.DESCONECTADO) { return true; }

                resultado = detenerLectura();

                if (resultado == false)
                {
                    this.reader.Disconnect();
                }

                finalizarReader();

                this.estadoReader = RFIDState.DESCONECTADO;

                resultado = true;
            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }

            return resultado;
        }

        private string obtenerInformacion()
        {
            StringBuilder cadenaInformacion = null;
            try
            {
                if (this.estadoReader == RFIDState.DESCONECTADO)
                {
                    log.Debug("Primero debe establecer conexion con el Reader");
                    return string.Empty;
                }

                cadenaInformacion = new StringBuilder();

                FeatureSet features = this.reader.QueryFeatureSet();

                cadenaInformacion.Append("Nombre del Modelo: " + features.ModelName);
                cadenaInformacion.Append(Environment.NewLine);
                cadenaInformacion.Append("Numero del Modelo: " + features.ModelNumber);
                cadenaInformacion.Append(Environment.NewLine);
                cadenaInformacion.Append("Numero de Serie: " + features.SerialNumber);
                cadenaInformacion.Append(Environment.NewLine);
                cadenaInformacion.Append("Indetificación del Reader: " + features.ReaderIdentity);
                cadenaInformacion.Append(Environment.NewLine);
                cadenaInformacion.Append("Version del Firmware: " + features.FirmwareVersion);
                cadenaInformacion.Append(Environment.NewLine);
                cadenaInformacion.Append("Version del Octane software: " + features.SoftwareVersion);
                cadenaInformacion.Append(Environment.NewLine);
                cadenaInformacion.Append("Cantidad de Antenas: " + features.AntennaCount);

                log.Debug(cadenaInformacion.ToString());
            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }

            return cadenaInformacion.ToString();
        }

        private bool obtenerEstadoConexion()
        {
            bool conectado = false;
            try
            {
                Status status = this.reader.QueryStatus();
                conectado = status.Connection == ApplicationConnectionState.Connected;
            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }

            return conectado;
        }

        private bool iniciarLectura()
        {
            bool resultado = false;
            try
            {
                if (this.estadoReader == RFIDState.DESCONECTADO) { return resultado; }

                if (!this.reader.QueryStatus().IsSingulating)
                {
                    this.reader.Start();
                }

                resultado = true;
            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }

            return resultado;
        }

        private bool detenerLectura()
        {
            bool resultado = false;
            try
            {
                if (this.estadoReader == RFIDState.DESCONECTADO)
                {
                    log.Debug("Primero debe establecer conexion con el Reader");
                    return resultado;
                }

                if (this.reader.QueryStatus().IsSingulating)
                {
                    this.reader.Stop();
                }

                resultado = true;

            }
            catch (Exception ex)
            {
                log.Error(ex.Message, ex);
            }

            return resultado;
        }

        private void guadarEPC(string codigoEPC)
        {
            ProgramEpcParams accessParams = new ProgramEpcParams();
            ProgramEpcResult accessResult;

            accessParams.TargetTag = null;
            accessParams.AccessPassword = null;
            accessParams.TimeoutInMs = TIEMPO_ESPERA_COMANDO;
            accessParams.AntennaPortNumber = NUMERO_ANTENA_COMANDO;
            accessParams.NewEpc = codigoEPC;
            accessParams.IsWriteVerified = true;
            accessParams.LockType = LockType.Locked;

            accessResult = this.reader.ProgramEpc(accessParams);

            log.Debug(string.Format("Programmed guadarEPC tag {0}: {1}", accessResult.TagAccessed.Epc,
                                                                         accessResult.Result));

            if (accessResult.Result != AccessResult.Success)
            {
                throw new Exception(string.Format("Error al ejecutar comando guadarEPC Tag {0}: {1}", accessResult.TagAccessed.Epc,
                                                                                                      accessResult.Result));
            }
        }

        private void guadarAccessPwd(string codigoEPC, string accessPassword)
        {
            ProgramAccessPasswordParams accessPwd = new ProgramAccessPasswordParams();
            ProgramAccessPasswordResult acResult;

            accessPwd.TargetTag = codigoEPC;
            accessPwd.AccessPassword = null;
            accessPwd.TimeoutInMs = TIEMPO_ESPERA_COMANDO;
            accessPwd.AntennaPortNumber = NUMERO_ANTENA_COMANDO;
            accessPwd.NewAccessPassword = accessPassword;
            // Ejecutar sentencia y obtener resultado
            acResult = this.reader.ProgramAccessPassword(accessPwd);

            log.Debug(string.Format("Programmed guadarAccessPwd tag {0}: {1}", acResult.TagAccessed.Epc,
                                                                               acResult.Result));

            if (acResult.Result != AccessResult.Success)
            {
                throw new Exception(string.Format("Error al ejecutar comando guadarAccessPwd Tag {0}: {1}", acResult.TagAccessed.Epc,
                                                                                                            acResult.Result));
            }
        }

        private void guadarKillPwd(string codigoEPC, string killPassword)
        {
            ProgramKillPasswordParams pwParams = new ProgramKillPasswordParams();
            ProgramKillPasswordResult pwResult;

            // Se especifica el codigo EPC a setear.
            pwParams.TargetTag = codigoEPC;
            // No access password required for this tag.
            pwParams.AccessPassword = null;
            // Espero 5 segundos si la operacion logro su objetivo.
            pwParams.TimeoutInMs = TIEMPO_ESPERA_COMANDO;
            pwParams.AntennaPortNumber = NUMERO_ANTENA_COMANDO;
            // Specify the new kill password (32-bits).
            pwParams.NewKillPassword = killPassword;
            // Ejecutar sentencia y obtener resultado
            pwResult = this.reader.ProgramKillPassword(pwParams);

            log.Debug(string.Format("Programmed guadarKillPwd tag {0}: {1}", pwResult.TagAccessed.Epc,
                                                                             pwResult.Result));

            if (pwResult.Result != AccessResult.Success)
            {
                throw new Exception(string.Format("Error al ejecutar comando guadarKillPwd Tag {0}: {1}", pwResult.TagAccessed.Epc,
                                                                                                          pwResult.Result));
            }
        }

        private void bloquearAccessPwd(string codigoEPC, string accessPassword)
        {
            // Define how we want to lock user memory.                 
            LockTagParams lockParams = new LockTagParams();
            lockParams.AccessPassword = accessPassword;
            lockParams.LockAccessPassword = LockType.Locked;
            lockParams.TimeoutInMs = TIEMPO_ESPERA_COMANDO;
            lockParams.TargetTag = codigoEPC;
            lockParams.AntennaPortNumber = NUMERO_ANTENA_COMANDO;
            // Lock user memory check the results.                 
            LockTagResult lockResult = this.reader.LockTag(lockParams);

            log.Debug(string.Format("Programmed bloquearAccessPwd tag {0}: {1}", lockResult.TagAccessed.Epc,
                                                                                 lockResult.LockResult.Result));

            if (lockResult.LockResult.Result != AccessResult.Success)
            {
                throw new Exception(string.Format("Error al ejecutar comando bloquearAccessPwd Tag {0}: {1}", lockResult.TagAccessed.Epc,
                                                                                                              lockResult.LockResult.Result));
            }
        }

        private void bloquearEPC(string codigoEPC, string accessPassword)
        {
            // Define how we want to lock user memory.                 
            LockTagParams lockParams = new LockTagParams();
            lockParams.AccessPassword = accessPassword;
            lockParams.LockEpcMemory = LockType.Locked;
            lockParams.TimeoutInMs = TIEMPO_ESPERA_COMANDO;
            lockParams.TargetTag = codigoEPC;
            lockParams.AntennaPortNumber = NUMERO_ANTENA_COMANDO;
            // Lock user memory check the results.                 
            LockTagResult lockResult = this.reader.LockTag(lockParams);

            log.Debug(string.Format("Programmed bloquearEPC tag {0}: {1}", lockResult.TagAccessed.Epc,
                                                                           lockResult.LockResult.Result));

            if (lockResult.LockResult.Result != AccessResult.Success)
            {
                throw new Exception(string.Format("Error al ejecutar comando bloquearEPC Tag {0}: {1}", lockResult.TagAccessed.Epc,
                                                                                                        lockResult.LockResult.Result));
            }
        }

        private void eliminarTAG(string codigoEPC, string killPassword)
        {
            KillTagParams killParams = new KillTagParams();
            // Use antenna #1.
            killParams.AntennaPortNumber = NUMERO_ANTENA_COMANDO;
            // Set the kill password.
            killParams.KillPassword = killPassword;
            // No access password required for this tag.
            killParams.AccessPassword = null;
            // Specify the target tag by EPC.
            killParams.TargetTag = codigoEPC;
            // Timeout in 5 seconds if the kill operation fails.
            killParams.TimeoutInMs = TIEMPO_ESPERA_COMANDO;
            // Perform the kill and check the results.
            KillTagResult killResult = this.reader.KillTag(killParams);

            log.Debug(string.Format("Programmed eliminarTAG tag {0}: {1}", killResult.TagAccessed.Epc,
                                                                           killResult.KillResult.Result));

            if (killResult.KillResult.Result != AccessResult.Success)
            {
                throw new Exception(string.Format("Error al ejecutar comando eliminarTAG Tag {0}: {1}", killResult.TagAccessed.Epc,
                                                                                                        killResult.KillResult.Result));
            }
        }
        #endregion
    }
}
